1. GTZAN Dataset - Music Genre Classification 데이터셋¶

  • 뮤직 장르의 Classification 을 위한 kaggle 데이터셋
    • 인간이 이해하기 힘든 Spectogram의 y축을 Mel Scale로 변환한 것(Non-linear transformation)

    • Mel Scale : https://newsight.tistory.com/294

  • .wav: 여러가지 장르의 형식의 오디오 파일
  • .png: Mel Spectogram 이미지
  • .csv: 각각의 오디오 features

1-1. 데이터셋 다운로드¶

In [1]:
import os
In [2]:
os.environ['KAGGLE_USERNAME'] = 'yunseopsong'
os.environ['KAGGLE_KEY'] = 'af83501befeb0bedb4e8100f474816a7'
In [3]:
!kaggle datasets download -d andradaolteanu/gtzan-dataset-music-genre-classification
Dataset URL: https://www.kaggle.com/datasets/andradaolteanu/gtzan-dataset-music-genre-classification
License(s): other
Downloading gtzan-dataset-music-genre-classification.zip to /content
100% 1.21G/1.21G [00:21<00:00, 59.2MB/s]
100% 1.21G/1.21G [00:21<00:00, 61.2MB/s]
In [4]:
!unzip -q /content/gtzan-dataset-music-genre-classification.zip
  • filename: 파일 이름
  • length: 길이
  • chroma_stft_mean: 크로마 STFT 평균
  • chroma_stft_var: 크로마 STFT 분산
  • rms_mean: RMS 평균
  • rms_var: RMS 분산
  • spectral_centroid_mean: 스펙트럴 중심 주파수 평균
  • spectral_centroid_var: 스펙트럴 중심 주파수 분산
  • spectral_bandwidth_mean: 스펙트럴 대역폭 평균
  • spectral_bandwidth_var: 스펙트럴 대역폭 분산
  • rolloff_mean: 롤오프 평균
  • rolloff_var: 롤오프 분산
  • zero_crossing_rate_mean: 제로 크로싱 비율 평균
  • zero_crossing_rate_var: 제로 크로싱 비율 분산
  • harmony_mean: 하모니 평균
  • harmony_var: 하모니 분산
  • perceptr_mean: 퍼셉트르 평균
  • perceptr_var: 퍼셉트르 분산
  • tempo: 템포
  • mfcc1_mean ~ mfcc20_mean: MFCC 1에서 20까지의 평균
  • mfcc1_var ~ mfcc20_var: MFCC 1에서 20까지의 분산
  • label: 레이블

2. 오디오 파일 이해하기¶

  • y: 소리가 떨리는 세기(진폭)를 시간 순서대로 나열한 것
  • Sampling rate: 1초당 샘플의 개수,단위 1초당 Hz 또는 KHz

2-2. librosa 패키지¶

  • 음악 및 오디오 분석을 위한 Python 패키지입니다. 이는 음악 정보 검색 시스템을 만드는 데 필요한 빌딩 블록을 제공합니다.
  • https://librosa.org/doc/latest/index.html
In [ ]:
import librosa

# 음원파일 로드
y, sr = librosa.load('/content/Data/genres_original/jazz/jazz.00002.wav')
In [ ]:
# y(소리의 세기) 값이 숫자로 이루어져있고, 음악의 길이 = 음파 길이/sr
print(y)
print(len(y))
print('Sampling rate (Hz): %d' %sr)
print('Audio length (seconds): %.2f' % (len(y) / sr)) #음악의 길이(초) = 음파의 길이/Sampling rate
[0.00335693 0.00491333 0.00378418 ... 0.05691528 0.05136108 0.03808594]
661794
Sampling rate (Hz): 22050
Audio length (seconds): 30.01

2-2.음악 들어보기¶

In [ ]:
import IPython.display as ipd
ipd.Audio(y, rate=sr)
Out[ ]:
Your browser does not support the audio element.

2-3. waveform 시각화¶

In [ ]:
import matplotlib.pyplot as plt
import librosa.display

plt.figure(figsize =(16,6))
librosa.display.waveshow(y=y,sr=sr)
plt.show()
No description has been provided for this image

2-4. Fourier Transform(푸리에 변환)¶

  • 입력 신호를 다양한 주파수를 가지는 주기 함수들로 분해하는 기법

  • 시간 영역 데이터를 주파수 영역으로 변경 : time(시간) domain -> frequency(진동수) domain 변경 시 얻는 정보가 많아져 분석 용이.

  • y축: 주파수(로그 스케일)

  • color축:데시벨(진폭)

  • Fast Fourier Transform(FFT) 은 Fourier Transform을 효율적으로 계산할 수 있는 알고리즘으로 Signal processing(신호 처리) 에 널리 사용
In [ ]:
import numpy as np

D = np.abs(librosa.stft(y, n_fft=2048, hop_length=512)) #n_fft : window size / 이 때, 음성의 길이를 얼마만큼으로 자를 것인가? 를 window라고 부른다.


print(D.shape)

plt.figure(figsize=(16,6))
plt.plot(D)
plt.show()
(1025, 1293)
No description has been provided for this image

2-5.Spectogram(스펙토그램)¶

  • 시간에 따라 변하는 신호를 non periodic(비주기적) 신호 라고한다

  • 푸리에 변환을 통해 특정 길이의 음성 조각이 각각의 주파수 성분을 얼마만큼 갖고 있는지를 의미하는 스펙트럼을 얻을 수 있다

  • FFT가 계속 쌓인 FFT 묶음으로 생각할 수 있다

  • 시간이 지남에 따라 서로 다른 주파수에서 달라지는 신호의 크기 또는 진폭을 시각적으로 나타내는 방법

  • 음성에 들어있는 정보들을 수학적인 신호 처리를 거쳐 추출할 수 있다

  • 여러 개의 스펙트럼을 시간 축에 나열하면 시간 변화에 따른 스펙트럼의 변화인 스펙트로그램이 시각화 된다

In [ ]:
DB = librosa.amplitude_to_db(D, ref=np.max)
# amplitude(진폭) -> DB(데시벨)로 바꿈

plt.figure(figsize=(16,6))
librosa.display.specshow(DB,sr=sr, hop_length=512, x_axis='time', y_axis='log')
plt.colorbar()
plt.show()
No description has been provided for this image

2-6. Mel Spectogram¶

  • 인간은 주파수를 linear scale (선형 척도) 로 인식하지 못함

  • 인간은 높은 주파수보다 낮은 주파수에서의 차이를 더 잘 감지

    • 예) 500Hz와 1000Hz의 차이는 쉽게 구분할 수 있지만 두 쌍 사이의 거리가 같더라도 10,000Hz와 10,500Hz의 차이는 거의 구분할 수 없슴
  • 1937년에 Stevens, Volkmann, Newmann은 pitch 의 동일한 거리가 청취자에게 동일한 거리에서 들리도록 하는 pitch 단위를 제안

  • 주파수에 대한 수학적 연산을 수행하여 주파수를 mel scale 로 변환

In [ ]:
S = librosa.feature.melspectrogram(y=y, sr=sr)
S_DB = librosa.amplitude_to_db(S, ref=np.max)

plt.figure(figsize=(16,6))
librosa.display.specshow(S_DB, sr=sr,hop_length=512, x_axis='time',y_axis='log')
plt.colorbar(format='%+2.0f dB')
plt.title('Mel Spectrogram')
plt.show()
No description has been provided for this image

2-6. jazz와 blues의 Mel Spectogram 비교¶

In [ ]:
y, sr = librosa.load('/content/Data/genres_original/blues/blues.00002.wav')
y, _ = librosa.effects.trim(y)

S = librosa.feature.melspectrogram(y=y, sr=sr)
S_DB = librosa.amplitude_to_db(S, ref=np.max)

plt.figure(figsize=(16,6))
librosa.display.specshow(S_DB, sr=sr,hop_length=512, x_axis='time',y_axis='log')
plt.colorbar(format='%+2.0f dB')
plt.title('Mel Spectrogram')
plt.show()
No description has been provided for this image
In [ ]:
y1, sr1 = librosa.load('/content/Data/genres_original/jazz/jazz.00002.wav')
y1, _ = librosa.effects.trim(y1)
S1 = librosa.feature.melspectrogram(y=y1, sr=sr1)
S_DB1 = librosa.amplitude_to_db(S1, ref=np.max)

y2, sr2 = librosa.load('/content/Data/genres_original/blues/blues.00002.wav')
y2, _ = librosa.effects.trim(y2)
S2 = librosa.feature.melspectrogram(y=y2, sr=sr2)
S_DB2 = librosa.amplitude_to_db(S2, ref=np.max)

# 그래프 그리기
plt.figure(figsize=(16, 6))

# 첫 번째 Mel Spectrogram 그래프
plt.subplot(1, 2, 1)
librosa.display.specshow(S_DB1, sr=sr1, hop_length=512, x_axis='time', y_axis='log')
plt.colorbar(format='%+2.0f dB')
plt.title('Jazz')

# 두 번째 Mel Spectrogram 그래프
plt.subplot(1, 2, 2)
librosa.display.specshow(S_DB2, sr=sr2, hop_length=512, x_axis='time', y_axis='log')
plt.colorbar(format='%+2.0f dB')
plt.title('Blues')

plt.tight_layout()
plt.show()
No description has been provided for this image

차이점:¶

  • 주파수 대역의 활용:

    • 재즈 (왼쪽 이미지): 주파수 대역이 넓게 사용되며, 특히 중고주파수 영역(1000Hz 이상)에서 뚜렷한 패턴이 보입니다. 이는 재즈 음악이 다양한 악기와 복잡한 하모니를 포함하기 때문입니다 .
    • 블루스 (오른쪽 이미지): 주파수 대역이 주로 중저주파수 영역(2000Hz 이하)에 집중되어 있습니다. 이는 블루스 음악이 주로 보컬과 기타 같은 중저음악기를 사용하기 때문입니다.
  • 시간에 따른 주파수 변화:

    • 재즈: 시간에 따라 주파수 스펙트럼이 다양하게 변하며, 다양한 리듬과 멜로디 라인이 교차합니다. 이는 재즈가 즉흥 연주와 복잡한 구조를 가지는 특징을 반영합니다.

    • 블루스: 주파수 변화가 비교적 일정하며 반복적인 패턴을 보입니다. 이는 블루스가 12마디 블루스와 같은 반복적인 구조를 가지는 특징을 반영합니다.

  • 스펙트로그램의 밀도:

    • 재즈: 스펙트로그램이 더 밀집되어 있으며, 고주파 영역에서도 많은 에너지를 가지고 있습니다. 이는 재즈 음악에서 빠른 템포와 복잡한 리듬을 나타냅니다.

    • 블루스: 스펙트로그램이 비교적 덜 밀집되어 있으며, 중저주파 영역에 에너지가 집중됩니다. 이는 블루스 음악이 더 느리고 단순한 리듬을 가지고 있음을 나타냅니다.

  • 요약:

  • 재즈 음악: 주파수 대역이 넓게 사용되며, 고주파 영역에서도 에너지가 많고, 시간에 따라 주파수 변화가 복잡합니다.

  • 블루스 음악: 주로 중저주파수 대역에 에너지가 집중되며, 반복적인 패턴과 일정한 주파수 변화를 보입니다.

3. 오디오 특성 추출(Audio Feature Extraction)¶

3-1. Tempo(BPM)¶

  • librosa.beat.beat_track 함수는 템포(beat per minute, BPM)와 Beat의 타임스탬프(오디오 신호에서 감지된 비트의 시간 위치를 나타내는 배열) 두 개의 값을 반환
In [ ]:
tempo ,beats = librosa.beat.beat_track(y=y,sr=sr)
print(tempo)
print(beats)
[161.49902344]
[  46   64   80   96  112  129  146  163  179  196  212  228  244  261
  278  295  311  327  343  360  376  393  410  427  443  460  476  492
  508  524  541  558  575  591  607  623  639  656  673  689  705  722
  739  755  771  788  805  822  838  856  872  888  904  921  938  954
  970  986 1002 1018 1034 1050 1064 1079 1095 1111 1127 1143 1158 1175
 1192 1209 1224 1238 1255]

3-2. 제로 교차율(Zero Crossing Rate)¶

  • 음파가 양에서 음으로 또는 음에서 양으로 바뀌는 비율

  • 간단하지만 많이 쓰인다.

In [ ]:
zero_crossings = librosa.zero_crossings(y, pad=False)

print(zero_crossings)
print(sum(zero_crossings)) # 음 <-> 양 이동한 횟수
[False False False ... False  True False]
50563
In [ ]:
# 10000~ 10040 사이 값을 살펴보기.
# 이 때 Zero Crossing은 0이 되는 선을 지나친 횟수를 의미

n0 = 10000
n1 = 10040

plt.figure(figsize=(16,6))
plt.plot(y[n0:n1])
plt.grid()
plt.show()
No description has been provided for this image
In [ ]:
# Zero Crossing 횟수
zero_crossings = librosa.zero_crossings(y[n0:n1], pad=False) #n0 ~ n1 사이 zero crossings
print('Zero Crossing 횟수:',sum(zero_crossings))
Zero Crossing 횟수: 5

3-3. Harmonic and Percussive Components¶

  • Harmonic Components (하모닉 구성 요소)

  • 하모닉 구성 요소는 음악의 주파수 스펙트럼에서 기본 주파수와 그 배수(고조파)로 나타나는 성분입니다.

  • 이 성분들은 주로 악기의 음색(Timbre)과 연관이 있습니다.

  • 하모닉 구성 요소는 다음과 같은 특징을 가집니다:

    • 음악의 색깔(Timbre): 하모닉 성분은 음색을 형성하여 각 악기의 고유한 소리를 만듭니다. 예를 들어, 피아노와 바이올린이 같은 음을 연주하더라도 그 소리가 다르게 들리는 이유는 각 악기의 하모닉 성분이 다르기 때문입니다.

    • 주기적 패턴: 하모닉 성분은 주기적이며, 주파수 영역에서 명확한 피크를 보입니다. 이는 사람이 음정을 인식할 수 있게 합니다.

    • 연속적 특징: 하모닉 성분은 일반적으로 시간에 따라 비교적 연속적이며, 부드러운 변화를 나타냅니다.

  • Percussive Components (퍼커시브 구성 요소)

  • 퍼커시브 구성 요소는 리듬과 감정을 나타내는 충격파 형태의 성분입니다.

  • 이 성분들은 주로 타악기와 같은 소리를 형성하며, 다음과 같은 특징을 가집니다:

    • 리듬과 박자: 퍼커시브 성분은 리듬과 박자를 결정합니다. 예를 들어, 드럼의 비트나 스네어 드럼의 타격 소리는 퍼커시브 성분입니다.

    • 비주기적 패턴: 퍼커시브 성분은 비주기적이며, 짧고 강한 에너지를 가집니다. 이는 음악에서 충격적이거나 강조된 소리를 형성합니다.

    • 비연속적 특징: 퍼커시브 성분은 시간에 따라 급격한 변화를 나타내며, 순간적인 에너지를 방출합니다.

In [ ]:
# 하모닉 및 퍼커시브 성분 분리
y_harmonic, y_percussive = librosa.effects.hpss(y)

# 하모닉 성분 시각화
plt.figure(figsize=(16, 6))
plt.subplot(2, 1, 1)
librosa.display.waveshow(y_harmonic, sr=sr, alpha=0.6)
plt.title('Harmonic Component')
plt.xlabel('Time (s)')
plt.ylabel('Amplitude')

# 퍼커시브 성분 시각화
plt.subplot(2, 1, 2)
librosa.display.waveshow(y_percussive, sr=sr, alpha=0.6)
plt.title('Percussive Component')
plt.xlabel('Time (s)')
plt.ylabel('Amplitude')

plt.tight_layout()
plt.show()
No description has been provided for this image
In [ ]:
plt.figure(figsize=(16,6))
plt.plot(y_harmonic, color='b')
plt.plot(y_percussive, color='r')
plt.show()
No description has been provided for this image

3-4.Spectral Centroid¶

  • 소리를 주파수 표현했을 때, 주파수의 가중평균을 계산하여 소리의 "무게 중심"이 어딘지를 알려주는 지표

  • 예를 들어, 블루스 음악은 무게 중심이 가운데 부분에 놓여있는 반면, 메탈 음악은 (끝 부분에서 달리기 때문에) 노래의 마지막 부분에 무게 중심이 실린다.

In [ ]:
# 스펙트럴 센트로이드 계산
spectral_centroids = librosa.feature.spectral_centroid(y=y, sr=sr)[0]

# 시각화를 위한 시간 변수 계산
frames = range(len(spectral_centroids))

# 프레임 수를 시간(초)으로 변환
t = librosa.frames_to_time(frames)

# 정규화 함수 정의
def normalize(x, axis=0):
  return sklearn.preprocessing.minmax_scale(x, axis=axis)

# 시각화
plt.figure(figsize=(16,6))
# librosa.display.waveshow: 오디오 신호를 파형(waveform)으로 시각화
librosa.display.waveshow(y=y, sr=sr, alpha=0.5, color='b')
# 시간 t를 x축으로, 정규화된 스펙트럴 센트로이드를 y축으로 설정하여
# 스펙트럴 센트로이드를 빨간색 선으로 시각화합니다
plt.plot(t, normalize(spectral_centroids), color='r')
plt.show()
No description has been provided for this image

스펙트럴 센트로이드 분석¶

그래프에서 빨간색 선으로 나타난 스펙트럴 센트로이드는 시간에 따른 주파수 스펙트럼의 무게 중심을 시각적으로 보여줍니다.

  • 초기 부분 (0~5초):

    스펙트럴 센트로이드가 급격히 증가하는 부분이 있습니다. 이 구간에서는 고주파 성분이 많음을 의미합니다. 따라서 이 구간의 소리는 상대적으로 밝고 경쾌하게 들릴 수 있습니다.

  • 중간 부분 (5~20초):

    스펙트럴 센트로이드가 비교적 일정하고 중간 정도의 값을 유지하고 있습니다. 이 구간에서는 저주파와 고주파 성분이 균형 있게 분포되어 있음을 나타냅니다. 따라서 이 구간의 소리는 중간 정도의 밝기를 가지고 있을 가능성이 큽니다.

  • 후기 부분 (20초 이후):

    스펙트럴 센트로이드가 여전히 일정하고 중간 정도의 값을 유지합니다. 이 구간에서도 저주파와 고주파 성분이 균형 있게 분포되어 있음을 나타냅니다. 종합적인 무게 중심 전체적으로 볼 때, 소리의 무게 중심은 중간 정도의 주파수 영역에 위치해 있습니다. 초기 구간을 제외하면 스펙트럴 센트로이드는 비교적 일정하게 유지되므로, 이 소리는 특정한 고주파 또는 저주파 성분에 치우치지 않고 균형 잡힌 주파수 분포를 가지고 있음을 알 수 있습니다.

종합적인 무게 중심¶

전체적으로 볼 때, 소리의 무게 중심은 중간 정도의 주파수 영역에 위치해 있습니다. 초기 구간을 제외하면 스펙트럴 센트로이드는 비교적 일정하게 유지되므로, 이 소리는 특정한 고주파 또는 저주파 성분에 치우치지 않고 균형 잡힌 주파수 분포를 가지고 있음을 알 수 있습니다.

만약 특정 구간에서 소리의 무게 중심을 더 정확하게 알고 싶다면, 스펙트럴 센트로이드 값을 평균 내거나 특정 구간의 값을 분석하여 소리의 무게 중심이 어느 주파수 대역에 위치해 있는지 파악할 수 있습니다.

In [ ]:
# 확인
import IPython.display as ipd
ipd.Audio(y, rate=sr)
Out[ ]:
Your browser does not support the audio element.

3-5. Spectral Rolloff¶

  • 신호 모양을 측정한다.

  • 총 스펙트럴 에너지 중 낮은 주파수(85% 이하)에 얼마나 많이 집중되어 있는가

In [ ]:
spectral_rolloff = librosa.feature.spectral_rolloff(y=y, sr=sr)[0]

plt.figure(figsize=(16,6))
librosa.display.waveshow(y=y,sr=sr,alpha=0.5,color='b')
plt.plot(t, normalize(spectral_rolloff),color='r')
plt.show()
No description has been provided for this image

3-6. Mel-Frequency Cepstral Coefficients(MFCCs)¶

  • MFCCs는 특징들의 작은 집합(약 10-20)으로 스펙트럴 포곡선의 전체적인 모양을 축약하여 보여준다.

  • 사람의 청각 구조를 반영하여 음성 정보 추출

  • MFCC 그래프를 보면 각 MFCC 계수가 시간에 따라 어떻게 변하는지 시각적으로 확인할 수 있다.

  • 일반적으로 음성의 다양한 발음이나 소리 특성에 따라 MFCC 계수의 패턴이 달라집니다.

In [ ]:
mfccs = librosa.feature.mfcc(y=y, sr=sr)
mfccs = normalize(mfccs,axis=1)

print('mean: %.2f' % mfccs.mean())
print('var: %.2f' % mfccs.var())

plt.figure(figsize=(16, 6))
librosa.display.specshow(mfccs, sr=sr, x_axis='time', y_axis='frames')
plt.colorbar()
plt.title('MFCC')
plt.show()
mean: 0.52
var: 0.03
No description has been provided for this image

3-7. 크로마 특징(Chroma features)¶

  • 음악에서 매우 중요한 역할을 하는 특징입니다. 여러 가지 음악적 분석 및 처리 작업에서 사용됨

    • 강렬하고 흥미로운 표현: 크로마 특징은 음악의 감정적 표현이나 음악적 특성을 감지하는 데 도움을 줍니다. 특히 화음 인식과 같은 작업에서 중요한 역할을 합니다.

    • 크로마 개념의 기반: 크로마 특징은 인간 청각의 특성을 기반으로 합니다. 인간은 주파수가 옥타브(2배) 차이가 나는 두 음을 유사한 음으로 인식하는 경향이 있습니다. 예를 들어, 동일한 음계의 다른 옥타브의 음들은 유사성을 가지고 있습니다.

    • 12개의 Bin으로 표현: 크로마 특징은 전체 주파수 스펙트럼을 12개의 Bin으로 나누어 표현합니다. 이는 음악에서 사용되는 12개의 서로 다른 반음(세미톤)을 나타내며, 이러한 각 Bin은 주어진 시점에서 음악적 특성을 나타냅니다.

    • 크로마와 반음: 크로마는 음악에서 반음(세미톤)의 개념과 밀접하게 연결됩니다. 12개의 Bin은 옥타브 내에서 반음 간격을 나타내며, 이는 음악에서 사용되는 다양한 음계나 화음의 구성을 이해하는 데 중요합니다.

  • 크로마 특징은 주로 음악 정보 검색(Music Information Retrieval, MIR) 및 음악 신호 처리에서 사용되며, 음악적 구조를 분석하고 이해하는 데 중요한 도구로 인정받고 있습니다.

In [ ]:
chromagram = librosa.feature.chroma_stft(y=y, sr=sr, hop_length=512)

plt.figure(figsize=(16,6))
librosa.display.specshow(chromagram,x_axis='time', y_axis='chroma', hop_length=512)
plt.show()
No description has been provided for this image

4. 음악장르 분류 알고리즘¶

In [ ]:
import pandas as pd

df = pd.read_csv('/content/Data/features_3_sec.csv')

df.head()
Out[ ]:
filename length chroma_stft_mean chroma_stft_var rms_mean rms_var spectral_centroid_mean spectral_centroid_var spectral_bandwidth_mean spectral_bandwidth_var ... mfcc16_var mfcc17_mean mfcc17_var mfcc18_mean mfcc18_var mfcc19_mean mfcc19_var mfcc20_mean mfcc20_var label
0 blues.00000.0.wav 66149 0.335406 0.091048 0.130405 0.003521 1773.065032 167541.630869 1972.744388 117335.771563 ... 39.687145 -3.241280 36.488243 0.722209 38.099152 -5.050335 33.618073 -0.243027 43.771767 blues
1 blues.00000.1.wav 66149 0.343065 0.086147 0.112699 0.001450 1816.693777 90525.690866 2010.051501 65671.875673 ... 64.748276 -6.055294 40.677654 0.159015 51.264091 -2.837699 97.030830 5.784063 59.943081 blues
2 blues.00000.2.wav 66149 0.346815 0.092243 0.132003 0.004620 1788.539719 111407.437613 2084.565132 75124.921716 ... 67.336563 -1.768610 28.348579 2.378768 45.717648 -1.938424 53.050835 2.517375 33.105122 blues
3 blues.00000.3.wav 66149 0.363639 0.086856 0.132565 0.002448 1655.289045 111952.284517 1960.039988 82913.639269 ... 47.739452 -3.841155 28.337118 1.218588 34.770935 -3.580352 50.836224 3.630866 32.023678 blues
4 blues.00000.4.wav 66149 0.335579 0.088129 0.143289 0.001701 1630.656199 79667.267654 1948.503884 60204.020268 ... 30.336359 0.664582 45.880913 1.689446 51.363583 -3.392489 26.738789 0.536961 29.146694 blues

5 rows × 60 columns

In [ ]:
df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 9990 entries, 0 to 9989
Data columns (total 60 columns):
 #   Column                   Non-Null Count  Dtype  
---  ------                   --------------  -----  
 0   filename                 9990 non-null   object 
 1   length                   9990 non-null   int64  
 2   chroma_stft_mean         9990 non-null   float64
 3   chroma_stft_var          9990 non-null   float64
 4   rms_mean                 9990 non-null   float64
 5   rms_var                  9990 non-null   float64
 6   spectral_centroid_mean   9990 non-null   float64
 7   spectral_centroid_var    9990 non-null   float64
 8   spectral_bandwidth_mean  9990 non-null   float64
 9   spectral_bandwidth_var   9990 non-null   float64
 10  rolloff_mean             9990 non-null   float64
 11  rolloff_var              9990 non-null   float64
 12  zero_crossing_rate_mean  9990 non-null   float64
 13  zero_crossing_rate_var   9990 non-null   float64
 14  harmony_mean             9990 non-null   float64
 15  harmony_var              9990 non-null   float64
 16  perceptr_mean            9990 non-null   float64
 17  perceptr_var             9990 non-null   float64
 18  tempo                    9990 non-null   float64
 19  mfcc1_mean               9990 non-null   float64
 20  mfcc1_var                9990 non-null   float64
 21  mfcc2_mean               9990 non-null   float64
 22  mfcc2_var                9990 non-null   float64
 23  mfcc3_mean               9990 non-null   float64
 24  mfcc3_var                9990 non-null   float64
 25  mfcc4_mean               9990 non-null   float64
 26  mfcc4_var                9990 non-null   float64
 27  mfcc5_mean               9990 non-null   float64
 28  mfcc5_var                9990 non-null   float64
 29  mfcc6_mean               9990 non-null   float64
 30  mfcc6_var                9990 non-null   float64
 31  mfcc7_mean               9990 non-null   float64
 32  mfcc7_var                9990 non-null   float64
 33  mfcc8_mean               9990 non-null   float64
 34  mfcc8_var                9990 non-null   float64
 35  mfcc9_mean               9990 non-null   float64
 36  mfcc9_var                9990 non-null   float64
 37  mfcc10_mean              9990 non-null   float64
 38  mfcc10_var               9990 non-null   float64
 39  mfcc11_mean              9990 non-null   float64
 40  mfcc11_var               9990 non-null   float64
 41  mfcc12_mean              9990 non-null   float64
 42  mfcc12_var               9990 non-null   float64
 43  mfcc13_mean              9990 non-null   float64
 44  mfcc13_var               9990 non-null   float64
 45  mfcc14_mean              9990 non-null   float64
 46  mfcc14_var               9990 non-null   float64
 47  mfcc15_mean              9990 non-null   float64
 48  mfcc15_var               9990 non-null   float64
 49  mfcc16_mean              9990 non-null   float64
 50  mfcc16_var               9990 non-null   float64
 51  mfcc17_mean              9990 non-null   float64
 52  mfcc17_var               9990 non-null   float64
 53  mfcc18_mean              9990 non-null   float64
 54  mfcc18_var               9990 non-null   float64
 55  mfcc19_mean              9990 non-null   float64
 56  mfcc19_var               9990 non-null   float64
 57  mfcc20_mean              9990 non-null   float64
 58  mfcc20_var               9990 non-null   float64
 59  label                    9990 non-null   object 
dtypes: float64(57), int64(1), object(2)
memory usage: 4.6+ MB
  • filename: 파일 이름
  • length: 길이
  • chroma_stft_mean: 크로마 STFT 평균
  • chroma_stft_var: 크로마 STFT 분산
  • rms_mean: RMS 평균
  • rms_var: RMS 분산
  • spectral_centroid_mean: 스펙트럴 중심 주파수 평균
  • spectral_centroid_var: 스펙트럴 중심 주파수 분산
  • spectral_bandwidth_mean: 스펙트럴 대역폭 평균
  • spectral_bandwidth_var: 스펙트럴 대역폭 분산
  • rolloff_mean: 롤오프 평균
  • rolloff_var: 롤오프 분산
  • zero_crossing_rate_mean: 제로 크로싱 비율 평균
  • zero_crossing_rate_var: 제로 크로싱 비율 분산
  • harmony_mean: 하모니 평균
  • harmony_var: 하모니 분산
  • perceptr_mean: 퍼셉트르 평균
  • perceptr_var: 퍼셉트르 분산
  • tempo: 템포
  • mfcc1_mean ~ mfcc20_mean: MFCC 1에서 20까지의 평균
  • mfcc1_var ~ mfcc20_var: MFCC 1에서 20까지의 분산
  • label: 레이블
In [ ]:
df.isna().mean() # NaN 확인
Out[ ]:
filename                   0.0
length                     0.0
chroma_stft_mean           0.0
chroma_stft_var            0.0
rms_mean                   0.0
rms_var                    0.0
spectral_centroid_mean     0.0
spectral_centroid_var      0.0
spectral_bandwidth_mean    0.0
spectral_bandwidth_var     0.0
rolloff_mean               0.0
rolloff_var                0.0
zero_crossing_rate_mean    0.0
zero_crossing_rate_var     0.0
harmony_mean               0.0
harmony_var                0.0
perceptr_mean              0.0
perceptr_var               0.0
tempo                      0.0
mfcc1_mean                 0.0
mfcc1_var                  0.0
mfcc2_mean                 0.0
mfcc2_var                  0.0
mfcc3_mean                 0.0
mfcc3_var                  0.0
mfcc4_mean                 0.0
mfcc4_var                  0.0
mfcc5_mean                 0.0
mfcc5_var                  0.0
mfcc6_mean                 0.0
mfcc6_var                  0.0
mfcc7_mean                 0.0
mfcc7_var                  0.0
mfcc8_mean                 0.0
mfcc8_var                  0.0
mfcc9_mean                 0.0
mfcc9_var                  0.0
mfcc10_mean                0.0
mfcc10_var                 0.0
mfcc11_mean                0.0
mfcc11_var                 0.0
mfcc12_mean                0.0
mfcc12_var                 0.0
mfcc13_mean                0.0
mfcc13_var                 0.0
mfcc14_mean                0.0
mfcc14_var                 0.0
mfcc15_mean                0.0
mfcc15_var                 0.0
mfcc16_mean                0.0
mfcc16_var                 0.0
mfcc17_mean                0.0
mfcc17_var                 0.0
mfcc18_mean                0.0
mfcc18_var                 0.0
mfcc19_mean                0.0
mfcc19_var                 0.0
mfcc20_mean                0.0
mfcc20_var                 0.0
label                      0.0
dtype: float64
In [ ]:
X = df.drop(columns=['filename','length','label'])
y = df['label']

scaler = sklearn.preprocessing.MinMaxScaler()
np_scaled = scaler.fit_transform(X)

X = pd.DataFrame(np_scaled, columns=X.columns)

print(X.nunique())
X.head()
chroma_stft_mean           9845
chroma_stft_var            9831
rms_mean                   9846
rms_var                    9846
spectral_centroid_mean     9847
spectral_centroid_var      9847
spectral_bandwidth_mean    9847
spectral_bandwidth_var     9847
rolloff_mean               9181
rolloff_var                9847
zero_crossing_rate_mean    8783
zero_crossing_rate_var     9846
harmony_mean               9847
harmony_var                9847
perceptr_mean              9847
perceptr_var               9846
tempo                        61
mfcc1_mean                 9845
mfcc1_var                  9846
mfcc2_mean                 9838
mfcc2_var                  9845
mfcc3_mean                 9847
mfcc3_var                  9842
mfcc4_mean                 9844
mfcc4_var                  9840
mfcc5_mean                 9845
mfcc5_var                  9844
mfcc6_mean                 9844
mfcc6_var                  9843
mfcc7_mean                 9847
mfcc7_var                  9844
mfcc8_mean                 9847
mfcc8_var                  9847
mfcc9_mean                 9846
mfcc9_var                  9843
mfcc10_mean                9847
mfcc10_var                 9845
mfcc11_mean                9846
mfcc11_var                 9846
mfcc12_mean                9847
mfcc12_var                 9844
mfcc13_mean                9847
mfcc13_var                 9847
mfcc14_mean                9846
mfcc14_var                 9846
mfcc15_mean                9846
mfcc15_var                 9842
mfcc16_mean                9847
mfcc16_var                 9845
mfcc17_mean                9847
mfcc17_var                 9847
mfcc18_mean                9847
mfcc18_var                 9845
mfcc19_mean                9844
mfcc19_var                 9843
mfcc20_mean                9846
mfcc20_var                 9847
dtype: int64
Out[ ]:
chroma_stft_mean chroma_stft_var rms_mean rms_var spectral_centroid_mean spectral_centroid_var spectral_bandwidth_mean spectral_bandwidth_var rolloff_mean rolloff_var ... mfcc16_mean mfcc16_var mfcc17_mean mfcc17_var mfcc18_mean mfcc18_var mfcc19_mean mfcc19_var mfcc20_mean mfcc20_var
0 0.355399 0.716757 0.293133 0.107955 0.262173 0.034784 0.459205 0.094130 0.346153 0.083164 ... 0.363613 0.056198 0.397172 0.066062 0.371828 0.055344 0.380831 0.026797 0.506746 0.047781
1 0.367322 0.670347 0.253040 0.044447 0.270969 0.018716 0.470831 0.052261 0.363722 0.051694 ... 0.468596 0.092912 0.351681 0.074001 0.362068 0.076365 0.418452 0.082414 0.593029 0.065548
2 0.373159 0.728067 0.296753 0.141663 0.265293 0.023073 0.494051 0.059922 0.378215 0.060820 ... 0.479681 0.096704 0.420979 0.050639 0.400536 0.067509 0.433742 0.043841 0.546264 0.036062
3 0.399349 0.677066 0.298024 0.075042 0.238427 0.023187 0.455246 0.066234 0.329587 0.070906 ... 0.386258 0.067995 0.387474 0.050617 0.380430 0.050030 0.405824 0.041898 0.562204 0.034873
4 0.355668 0.689113 0.322308 0.052149 0.233460 0.016451 0.451651 0.047830 0.318453 0.046916 ... 0.438567 0.042500 0.460314 0.083860 0.388590 0.076524 0.409019 0.020763 0.517913 0.031713

5 rows × 57 columns

In [ ]:
# label 이 문자열 이므로 0 ~ 9 까지의 숫자형으로 변환
from sklearn.preprocessing import LabelEncoder

label_encoder = LabelEncoder()

y = label_encoder.fit_transform(y)

print(y)
[0 0 0 ... 9 9 9]
In [ ]:
from sklearn.model_selection import train_test_split

X_train , X_test , y_train, y_test = train_test_split(X,y , test_size=0.2, random_state=2024)

print(X_train.shape, y_train.shape)
print(X_test.shape, y_test.shape)
(7992, 57) (7992,)
(1998, 57) (1998,)

XGBoost 머신러닝 사용¶

In [ ]:
from xgboost import XGBClassifier
from sklearn.metrics import accuracy_score

xgb = XGBClassifier(n_estimators=1000, learning_rate=0.05)
xgb.fit(X_train, y_train) #학습

y_preds = xgb.predict(X_test) #검증

print('Accuracy: %.2f' % accuracy_score(y_test,y_preds))
Accuracy: 0.91

ResNet모델 사용¶

In [ ]:
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import TensorDataset, DataLoader, random_split
from torchvision import transforms, models, datasets

device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
print(device)
cpu
In [ ]:
# Tensor로 변환
X_train_tensor = torch.tensor(X_train.values, dtype=torch.float32)
y_train_tensor = torch.tensor(y_train, dtype=torch.long)
X_test_tensor = torch.tensor(X_test.values, dtype=torch.float32)
y_test_tensor = torch.tensor(y_test, dtype=torch.long)

# 데이터셋 설정
train_dataset = TensorDataset(X_train_tensor, y_train_tensor)
test_dataset = TensorDataset(X_test_tensor, y_test_tensor)

# 데이터로더 설정
batch_size = 32
train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False)
dataloaders = {
    'train': train_loader,
    'test': test_loader
}
In [ ]:
#  설정
class CustomResNet(nn.Module):
    def __init__(self, input_size, num_classes):
        super(CustomResNet, self).__init__()
        self.resnet = models.resnet18(pretrained=True)
        self.resnet.conv1 = nn.Conv2d(1, 64, kernel_size=(7, 7), stride=(2, 2), padding=(3, 3), bias=False)
        self.resnet.fc = nn.Linear(self.resnet.fc.in_features, num_classes)

    def forward(self, x):
        x = x.unsqueeze(1).unsqueeze(1)  # [batch_size, 1, 1, input_size]
        x = self.resnet(x)
        return x

input_size = X_train.shape[1]
num_classes = len(label_encoder.classes_)
model = CustomResNet(input_size, num_classes).to(device)

# 손실 함수 및 옵티마이저 설정
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)

# 학습
epochs = 10

for epoch in range(epochs):
    model.train()
    sum_losses = 0
    sum_accs = 0
    for x_batch, y_batch in train_loader:
        x_batch = x_batch.to(device)
        y_batch = y_batch.to(device)

        y_pred = model(x_batch)
        loss = criterion(y_pred, y_batch)

        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

        sum_losses += loss.item()
        y_pred_index = torch.argmax(y_pred, axis=1)
        acc = (y_batch == y_pred_index).float().mean().item() * 100
        sum_accs += acc

    avg_loss = sum_losses / len(train_loader)
    avg_acc = sum_accs / len(train_loader)
    print(f'Train: Epoch {epoch+1}/{epochs} Loss: {avg_loss:.4f} Accuracy: {avg_acc:.2f}%')

    # 검증
    model.eval()
    with torch.no_grad():
        sum_losses = 0
        sum_accs = 0
        for x_batch, y_batch in test_loader:
            x_batch = x_batch.to(device)
            y_batch = y_batch.to(device)

            y_pred = model(x_batch)
            loss = criterion(y_pred, y_batch)

            sum_losses += loss.item()
            y_pred_index = torch.argmax(y_pred, axis=1)
            acc = (y_batch == y_pred_index).float().mean().item() * 100
            sum_accs += acc

        avg_loss = sum_losses / len(test_loader)
        avg_acc = sum_accs / len(test_loader)
        print(f'Test:  Epoch {epoch+1}/{epochs} Loss: {avg_loss:.4f} Accuracy: {avg_acc:.2f}%')
/usr/local/lib/python3.10/dist-packages/torchvision/models/_utils.py:208: UserWarning: The parameter 'pretrained' is deprecated since 0.13 and may be removed in the future, please use 'weights' instead.
  warnings.warn(
/usr/local/lib/python3.10/dist-packages/torchvision/models/_utils.py:223: UserWarning: Arguments other than a weight enum or `None` for 'weights' are deprecated since 0.13 and may be removed in the future. The current behavior is equivalent to passing `weights=ResNet18_Weights.IMAGENET1K_V1`. You can also use `weights=ResNet18_Weights.DEFAULT` to get the most up-to-date weights.
  warnings.warn(msg)
Train: Epoch 1/10 Loss: 1.4128 Accuracy: 51.41%
Test:  Epoch 1/10 Loss: 1.0071 Accuracy: 65.39%
Train: Epoch 2/10 Loss: 1.1018 Accuracy: 62.02%
Test:  Epoch 2/10 Loss: 1.1884 Accuracy: 56.15%
Train: Epoch 3/10 Loss: 0.9515 Accuracy: 67.19%
Test:  Epoch 3/10 Loss: 1.1428 Accuracy: 59.81%
Train: Epoch 4/10 Loss: 0.8121 Accuracy: 72.75%
Test:  Epoch 4/10 Loss: 0.9976 Accuracy: 65.36%
Train: Epoch 5/10 Loss: 0.7107 Accuracy: 75.57%
Test:  Epoch 5/10 Loss: 0.8159 Accuracy: 73.19%
Train: Epoch 6/10 Loss: 0.6307 Accuracy: 78.68%
Test:  Epoch 6/10 Loss: 0.8050 Accuracy: 73.04%
Train: Epoch 7/10 Loss: 0.5578 Accuracy: 81.05%
Test:  Epoch 7/10 Loss: 0.5901 Accuracy: 79.69%
Train: Epoch 8/10 Loss: 0.4807 Accuracy: 83.28%
Test:  Epoch 8/10 Loss: 0.5898 Accuracy: 79.47%
Train: Epoch 9/10 Loss: 0.4148 Accuracy: 86.22%
Test:  Epoch 9/10 Loss: 0.5107 Accuracy: 83.39%
Train: Epoch 10/10 Loss: 0.3663 Accuracy: 87.45%
Test:  Epoch 10/10 Loss: 0.5031 Accuracy: 83.21%
In [ ]:
import librosa
import numpy as np
import pandas as pd

def predict_genre_from_audio(audio_file, model, label_encoder, scaler):
    # 오디오 파일 불러오기
    y, sr = librosa.load(audio_file)

    # 특성 추출
    chroma_stft = librosa.feature.chroma_stft(y=y, sr=sr)
    rms = librosa.feature.rms(y=y)
    spectral_centroid = librosa.feature.spectral_centroid(y=y, sr=sr)
    spectral_bandwidth = librosa.feature.spectral_bandwidth(y=y, sr=sr)
    rolloff = librosa.feature.spectral_rolloff(y=y, sr=sr)
    zero_crossing_rate = librosa.feature.zero_crossing_rate(y)
    mfcc = librosa.feature.mfcc(y=y, sr=sr, n_mfcc=20)
    tempo, _ = librosa.beat.beat_track(y=y, sr=sr)
    harmony, perceptr = librosa.effects.hpss(y)

    def compute_statistics(feature):
        mean = np.mean(feature)
        var = np.var(feature)
        return mean, var

    chroma_stft_mean, chroma_stft_var = compute_statistics(chroma_stft)
    rms_mean, rms_var = compute_statistics(rms)
    spectral_centroid_mean, spectral_centroid_var = compute_statistics(spectral_centroid)
    spectral_bandwidth_mean, spectral_bandwidth_var = compute_statistics(spectral_bandwidth)
    rolloff_mean, rolloff_var = compute_statistics(rolloff)
    zero_crossing_rate_mean, zero_crossing_rate_var = compute_statistics(zero_crossing_rate)
    harmony_mean, harmony_var = compute_statistics(harmony)
    perceptr_mean, perceptr_var = compute_statistics(perceptr)

    mfcc_means = np.mean(mfcc, axis=1)
    mfcc_vars = np.var(mfcc, axis=1)

    # 특성을 데이터프레임으로 변환
    features = {
        'chroma_stft_mean': [chroma_stft_mean],
        'chroma_stft_var': [chroma_stft_var],
        'rms_mean': [rms_mean],
        'rms_var': [rms_var],
        'spectral_centroid_mean': [spectral_centroid_mean],
        'spectral_centroid_var': [spectral_centroid_var],
        'spectral_bandwidth_mean': [spectral_bandwidth_mean],
        'spectral_bandwidth_var': [spectral_bandwidth_var],
        'rolloff_mean': [rolloff_mean],
        'rolloff_var': [rolloff_var],
        'zero_crossing_rate_mean': [zero_crossing_rate_mean],
        'zero_crossing_rate_var': [zero_crossing_rate_var],
        "harmony_mean": [harmony_mean],
        "harmony_var": [harmony_var],
        "perceptr_mean": [perceptr_mean],
        "perceptr_var": [perceptr_var],
        "tempo": [tempo]
    }

    for i in range(1, 21):
        features[f'mfcc{i}_mean'] = [mfcc_means[i-1]]
        features[f'mfcc{i}_var'] = [mfcc_vars[i-1]]

    df = pd.DataFrame(features)

    # 스케일링 (Scaling)
    scaled_features = scaler.transform(df.values.reshape(1, -1))

    # 텐서로 변환
    scaled_tensor = torch.tensor(scaled_features, dtype=torch.float32)

    # 예측
    model.eval()  # 모델을 평가 모드로 설정합니다
    with torch.no_grad():
        outputs = model.forward(scaled_tensor)
        _, preds = torch.max(outputs, 1)

    predicted_genre = label_encoder.inverse_transform(preds)[0]

    return predicted_genre
In [ ]:
audio_path = '/content/spring-mood-wav-212731.wav'

predict_genre_from_audio(audio_path, model, label_encoder, scaler)
/usr/local/lib/python3.10/dist-packages/sklearn/base.py:439: UserWarning: X does not have valid feature names, but MinMaxScaler was fitted with feature names
  warnings.warn(
Out[ ]:
'jazz'

스펙토그램 이미지로 분류¶

In [ ]:
import os
import librosa
import numpy as np
import torch
from torch.utils.data import Dataset, DataLoader
from torchvision import transforms

class AudioDataset(Dataset):
    def __init__(self, file_paths, labels, sample_rate=22050, n_fft=2048, hop_length=512, n_mels=128, duration=5):
        self.file_paths = file_paths
        self.labels = labels
        self.sample_rate = sample_rate
        self.n_fft = n_fft
        self.hop_length = hop_length
        self.n_mels = n_mels
        self.duration = duration  # 모든 오디오를 동일한 길이로 자르거나 패딩할 길이 (초 단위)

    def __len__(self):
        return len(self.file_paths)

    def __getitem__(self, index):
        audio_path = self.file_paths[index]
        label = self.labels[index]

        # 오디오 파일을 로드하고 스펙트로그램 생성
        y, sr = librosa.load(audio_path, sr=self.sample_rate, duration=self.duration)
        spectrogram = self.audio_to_spectrogram(y)

        # 파이토치 텐서로 변환
        spectrogram = torch.FloatTensor(spectrogram)

        return spectrogram, label

    def audio_to_spectrogram(self, y):
        # Compute spectrogram
        spectrogram = librosa.feature.melspectrogram(y=y, sr=self.sample_rate, n_fft=self.n_fft, hop_length=self.hop_length, n_mels=self.n_mels)
        spectrogram = librosa.power_to_db(spectrogram, ref=np.max)  # Convert to dB scale
        return spectrogram

def load_data(data_dir, genres):
    file_paths = []
    labels = []

    for genre_idx, genre in enumerate(genres):
        genre_dir = os.path.join(data_dir, genre)
        for file_name in os.listdir(genre_dir):
            if file_name.endswith('.wav'):  # WAV 파일 필터링
                file_path = os.path.join(genre_dir, file_name)
                try:
                    # 오디오 파일을 로드하여 포맷 에러가 발생하지 않는지 확인
                    y, sr = librosa.load(file_path)
                    file_paths.append(file_path)
                    labels.append(genre_idx)
                except Exception as e:
                    print(f"Error loading {file_path}: {str(e)}")
                    continue

    return file_paths, labels
In [ ]:
# 데이터 경로와 장르 설정
data_dir = '/content/Data/genres_original'
genres = 'blues classical country disco hiphop jazz metal pop reggae rock'.split()

# 데이터 로드
file_paths, labels = load_data(data_dir, genres)

# 데이터셋 및 DataLoader 설정
dataset = AudioDataset(file_paths, labels)
train_loader = DataLoader(dataset, batch_size=16, shuffle=True)
<ipython-input-314-280b798a5005>:51: UserWarning: PySoundFile failed. Trying audioread instead.
  y, sr = librosa.load(file_path)
/usr/local/lib/python3.10/dist-packages/librosa/core/audio.py:184: FutureWarning: librosa.core.audio.__audioread_load
	Deprecated as of librosa version 0.10.0.
	It will be removed in librosa version 1.0.
  y, sr_native = __audioread_load(path, offset, duration, dtype)
Error loading /content/Data/genres_original/jazz/jazz.00054.wav: 
In [19]:
class AudioCNNLSTM(nn.Module):
    def __init__(self, num_classes):
        super(AudioCNNLSTM, self).__init__()
        # LSTM layers
        self.lstm = nn.LSTM(input_size=128, hidden_size=64, num_layers=2, batch_first=True)
        # CNN layers
        self.conv1 = nn.Conv2d(1, 16, kernel_size=3, stride=1, padding=1)
        self.conv2 = nn.Conv2d(16, 32, kernel_size=3, stride=1, padding=1)
        self.fc1 = nn.Linear(32 * 56 * 56, 128)
        self.fc2 = nn.Linear(128, num_classes)

    def forward(self, x):
        # LSTM input: (batch_size, seq_length, input_size)
        # CNN input: (batch_size, channels, height, width)
        x_lstm, _ = self.lstm(x)

        # Convert input for CNN
        x_cnn = x.unsqueeze(1)  # Add channel dimension for CNN input
        x_cnn = F.relu(self.conv1(x_cnn))
        x_cnn = F.max_pool2d(x_cnn, kernel_size=2, stride=2)
        x_cnn = F.relu(self.conv2(x_cnn))
        x_cnn = F.max_pool2d(x_cnn, kernel_size=2, stride=2)
        x_cnn = x_cnn.view(-1, 32 * 56 * 56)  # Flatten for fully connected layers

        # Concatenate LSTM and CNN outputs
        x_combined = torch.cat((x_lstm[:, -1, :], x_cnn), dim=1)

        # Fully connected layers
        x = F.relu(self.fc1(x_combined))
        x = self.fc2(x)
        return x
In [ ]:
# 모델 초기화 및 학습 설정
model = AudioCNNLSTM(num_classes=len(genres))
optimizer = optim.Adam(model.parameters(), lr=0.001)
criterion = nn.CrossEntropyLoss()

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model.to(device)

# 학습
epochs = 10
for epoch in range(epochs):
    model.train()
    sum_losses = 0
    sum_accs = 0
    for batch_idx, (spectrogram, label) in enumerate(train_loader):
        spectrogram = spectrogram.unsqueeze(1).to(device)
        label = label.to(device)

        optimizer.zero_grad()
        output = model(spectrogram)
        loss = criterion(output, label)
        loss.backward()
        optimizer.step()

        sum_losses += loss.item()
        output_pred_index = torch.argmax(output, axis=1)
        acc = (label == output_pred_index).float().mean().item() * 100
        sum_accs += acc

        avg_loss = sum_losses / len(train_loader)
        avg_acc = sum_accs / len(train_loader)
        if batch_idx % 10 == 0:
            print(f'Epoch {epoch+1}/{epochs}, Batch {batch_idx+1}/{len(train_loader)}, Loss: {loss.item():.4f}, Accuracy: {avg_acc:.2f}%')

# 모델 저장
torch.save(model.state_dict(), '/content/drive/MyDrive/KDT/audio_model.pth')
Epoch 1/10, Batch 1/63, Loss: 2.3100, Accuracy: 0.20%
Epoch 1/10, Batch 11/63, Loss: 2.2977, Accuracy: 1.79%
Epoch 1/10, Batch 21/63, Loss: 2.3612, Accuracy: 3.57%
Epoch 1/10, Batch 31/63, Loss: 2.3090, Accuracy: 5.06%
Epoch 1/10, Batch 41/63, Loss: 2.3250, Accuracy: 6.65%
Epoch 1/10, Batch 51/63, Loss: 2.2303, Accuracy: 8.83%
Epoch 1/10, Batch 61/63, Loss: 2.0360, Accuracy: 12.30%
Epoch 2/10, Batch 1/63, Loss: 2.4707, Accuracy: 0.10%
Epoch 2/10, Batch 11/63, Loss: 1.9425, Accuracy: 4.27%
Epoch 2/10, Batch 21/63, Loss: 2.1176, Accuracy: 8.23%
Epoch 2/10, Batch 31/63, Loss: 1.7842, Accuracy: 13.49%
Epoch 2/10, Batch 41/63, Loss: 1.6068, Accuracy: 18.75%
Epoch 2/10, Batch 51/63, Loss: 1.3172, Accuracy: 24.11%
Epoch 2/10, Batch 61/63, Loss: 1.3957, Accuracy: 29.76%
Epoch 3/10, Batch 1/63, Loss: 1.7409, Accuracy: 0.60%
Epoch 3/10, Batch 11/63, Loss: 1.6368, Accuracy: 6.15%
Epoch 3/10, Batch 21/63, Loss: 1.4029, Accuracy: 13.19%
Epoch 3/10, Batch 31/63, Loss: 1.4936, Accuracy: 19.44%
Epoch 3/10, Batch 41/63, Loss: 1.4290, Accuracy: 26.09%
Epoch 3/10, Batch 51/63, Loss: 2.0983, Accuracy: 31.65%
Epoch 3/10, Batch 61/63, Loss: 1.4279, Accuracy: 37.10%
Epoch 4/10, Batch 1/63, Loss: 1.2811, Accuracy: 1.09%
Epoch 4/10, Batch 11/63, Loss: 1.2117, Accuracy: 9.03%
Epoch 4/10, Batch 21/63, Loss: 0.9676, Accuracy: 17.56%
Epoch 4/10, Batch 31/63, Loss: 1.2126, Accuracy: 24.40%
Epoch 4/10, Batch 41/63, Loss: 1.5404, Accuracy: 31.55%
Epoch 4/10, Batch 51/63, Loss: 1.6195, Accuracy: 38.29%
Epoch 4/10, Batch 61/63, Loss: 1.0158, Accuracy: 44.54%
Epoch 5/10, Batch 1/63, Loss: 1.2510, Accuracy: 0.89%
Epoch 5/10, Batch 11/63, Loss: 1.4604, Accuracy: 9.13%
Epoch 5/10, Batch 21/63, Loss: 1.0545, Accuracy: 19.84%
Epoch 5/10, Batch 31/63, Loss: 1.3569, Accuracy: 28.67%
Epoch 5/10, Batch 41/63, Loss: 1.0680, Accuracy: 37.60%
Epoch 5/10, Batch 51/63, Loss: 1.4138, Accuracy: 46.63%
Epoch 5/10, Batch 61/63, Loss: 1.1396, Accuracy: 55.26%
Epoch 6/10, Batch 1/63, Loss: 1.1038, Accuracy: 0.79%
Epoch 6/10, Batch 11/63, Loss: 0.7358, Accuracy: 12.80%
Epoch 6/10, Batch 21/63, Loss: 0.5615, Accuracy: 24.50%
Epoch 6/10, Batch 31/63, Loss: 1.3492, Accuracy: 35.52%
Epoch 6/10, Batch 41/63, Loss: 0.7026, Accuracy: 46.03%
Epoch 6/10, Batch 51/63, Loss: 0.8608, Accuracy: 56.65%
Epoch 6/10, Batch 61/63, Loss: 0.7073, Accuracy: 67.86%
Epoch 7/10, Batch 1/63, Loss: 0.5982, Accuracy: 1.09%
Epoch 7/10, Batch 11/63, Loss: 0.5602, Accuracy: 14.78%
Epoch 7/10, Batch 21/63, Loss: 0.5730, Accuracy: 27.78%
Epoch 7/10, Batch 31/63, Loss: 0.3951, Accuracy: 41.17%
Epoch 7/10, Batch 41/63, Loss: 0.7555, Accuracy: 53.08%
Epoch 7/10, Batch 51/63, Loss: 0.6302, Accuracy: 64.48%
Epoch 7/10, Batch 61/63, Loss: 0.3383, Accuracy: 77.98%
Epoch 8/10, Batch 1/63, Loss: 0.4787, Accuracy: 1.29%
Epoch 8/10, Batch 11/63, Loss: 0.3373, Accuracy: 14.88%
Epoch 8/10, Batch 21/63, Loss: 0.1983, Accuracy: 29.17%
Epoch 8/10, Batch 31/63, Loss: 0.2863, Accuracy: 43.06%
Epoch 8/10, Batch 41/63, Loss: 0.3027, Accuracy: 57.24%
Epoch 8/10, Batch 51/63, Loss: 0.1577, Accuracy: 71.73%
Epoch 8/10, Batch 61/63, Loss: 0.2891, Accuracy: 86.41%
Epoch 9/10, Batch 1/63, Loss: 0.1731, Accuracy: 1.59%
Epoch 9/10, Batch 11/63, Loss: 0.4984, Accuracy: 15.77%
Epoch 9/10, Batch 21/63, Loss: 0.3542, Accuracy: 31.05%
Epoch 9/10, Batch 31/63, Loss: 0.0991, Accuracy: 46.43%
Epoch 9/10, Batch 41/63, Loss: 0.0787, Accuracy: 61.81%
Epoch 9/10, Batch 51/63, Loss: 0.0503, Accuracy: 77.18%
Epoch 9/10, Batch 61/63, Loss: 0.0450, Accuracy: 92.36%
Epoch 10/10, Batch 1/63, Loss: 0.0863, Accuracy: 1.59%
Epoch 10/10, Batch 11/63, Loss: 0.0220, Accuracy: 17.46%
Epoch 10/10, Batch 21/63, Loss: 0.1746, Accuracy: 33.13%
Epoch 10/10, Batch 31/63, Loss: 0.1291, Accuracy: 48.81%
Epoch 10/10, Batch 41/63, Loss: 0.0317, Accuracy: 64.38%
Epoch 10/10, Batch 51/63, Loss: 0.1911, Accuracy: 79.76%
Epoch 10/10, Batch 61/63, Loss: 0.0359, Accuracy: 95.54%
In [ ]:
# 스펙트로그램을 텐서로 변환하는 함수 정의
def audio_to_spectrogram_tensor(audio_path, sample_rate=22050, n_fft=2048, hop_length=512, n_mels=128, duration=5):
    # Load audio file with fixed duration
    y, sr = librosa.load(audio_path, sr=sample_rate, duration=duration)

    # Compute spectrogram
    spectrogram = librosa.feature.melspectrogram(y=y, sr=sr, n_fft=n_fft, hop_length=hop_length, n_mels=n_mels)
    spectrogram = librosa.power_to_db(spectrogram, ref=np.max)  # Convert to dB scale

    # Convert spectrogram to tensor
    transform = transforms.Compose([
        transforms.ToPILImage(),  # PIL 이미지로 변환
        transforms.Grayscale(num_output_channels=1),  # 그레이스케일로 변환
        transforms.ToTensor(),
        transforms.Normalize(mean=[0.485], std=[0.229]),  # 표준화
    ])
    tensor = transform(spectrogram)
    return tensor

# 오디오 파일 경로 설정
audio_file = '/content/jazz.00004.wav'  # 예제 오디오 파일 경로

# 오디오 파일을 스펙트로그램 텐서로 변환
input_tensor = audio_to_spectrogram_tensor(audio_file)


model = AudioCNNLSTM(num_classes=len(genres))
model.eval()

# GPU 사용 여부 확인 및 설정
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model.to(device)
input_tensor = input_tensor.to(device)

# CNN 모델에 입력하기 위해 데이터 형태 조정
input_tensor = input_tensor.unsqueeze(0)  # Add batch dimension for CNN input

# 추론
with torch.no_grad():
    output = model(input_tensor)
    probabilities = F.softmax(output, dim=1)  # 각 클래스에 대한 확률 분포 계산
    _, predicted_class = torch.max(output, 1)  # 최대 확률을 가진 클래스의 인덱스

# 예측된 장르와 클래스 확률 출력
predicted_genre = genres[predicted_class.item()]
print(f'Predicted Genre: {predicted_genre}')
print(f'Class Probabilities: {probabilities.cpu().numpy()[0]}')
Predicted Genre: jazz
Class Probabilities: [0.10388141 0.10554514 0.09810621 0.08924551 0.09312897 0.10791548
 0.10385623 0.10160261 0.10388492 0.09283353]

결론¶

  • 정답률은 어느정도 나오나 정확하게 나오지는 않음

  • 오디오나 음악 분류 쪽은 선학습 가중치 데이터가 적어서 찾기가 힘들어 직접 데이터셋을 학습시키거나 다른 도메에서 미세조정을 통해 전이 학습을 해야함

  • VGGish나 WaveNet과 같은 오디오 분석 특화 모델을 사용하면 정확도가 더 올라갈 것으로 예상됨

  • 오디오 분석에는 여러 방식이 있지만 CNN을 포함한 다양한 딥러닝 기법이 스펙트로그램 데이터를 잘 처리하고 있으며, 다른 방법들보다 높은 정확도와 효율성을 줌으로 현재는 스펙토그램 분석 방식이 가장 널리 사용되고 있다고 함

참조¶

  • https://jonhyuk0922.tistory.com/114
  • https://medium.com/@suhagu01/ai-x-%EB%94%A5%EB%9F%AC%EB%8B%9D-projects2021-a9a87a02fe23
  • https://ko.wikipedia.org/
In [ ]: